/*
 * Copyright (c) 2019 https://github.com/arionik/avilib
 * Copyright (c) 2020-2021 https://gitee.com/fsfzp888
 * All rights reserved
 */

#include <algorithm>
#include <cassert>

#include "AVILib.h"

using namespace avilib;

#define STDINDEXSIZE 0x4000
#ifdef _WIN32
#    define filepos() tellp().seekpos()
#else
#    define filepos() tellp()
#endif

AviWriter::AviWriter()
{
    reset();
}

void AviWriter::reset()
{
    memset(&m_avimHeader, 0x0, sizeof(AVIMAINHEADER));
    m_avimHeader.fcc     = 'hiva';
    m_avimHeader.cb      = sizeof(AVIMAINHEADER) - 8;
    m_avimHeader.dwFlags = AVIF_HASINDEX /* | AVIF_MUSTUSEINDEX | AVIF_TRUSTCKTYPE*/ | AVIF_ISINTERLEAVED;

    memset(&m_bitmapInfo, 0x0, sizeof(BITMAPINFO));
    m_bitmapInfo.biSize        = sizeof(BITMAPINFO);
    m_bitmapInfo.biPlanes      = 1;
    m_bitmapInfo.biBitCount    = 24;  // ??
    m_bitmapInfo.biCompression = BI_RGB;

    memset(&m_waveFormat, 0x0, sizeof(WAVEFORMATEXTENSIBLE));
    m_waveFormat.Format.cbSize = sizeof(WAVEFORMATEX) - 4;

    uint8_t guid_pcm[] = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71};
    memcpy(m_waveFormat.SubFormat, &guid_pcm, 16);
}

AviWriter::~AviWriter()
{
    close();
}

bool AviWriter::openDML(bool yes)
{
    if (m_opened)
        return false;
    m_openDML = yes;
    return true;
}

uint32_t NULL32 = 0;
bool AviWriter::open(const char *filename)
{
    if (!m_streamTypes.size())
        return false;

    if (!m_openDML && !m_reservedFrames)
        return false;

    // more than two not supported
    // must be video or video + audio

    int32_t i_tmp = 0;
    std::ofstream::streamoff off_tmp;

    for (auto idx_type : m_streamTypes)
        if (i_tmp++ != idx_type.first)
            return false;  // not adjacent

    _f.open(filename, std::ofstream::binary | std::ofstream::trunc);
    if (!_f.good())
        return false;

    _f.write("RIFF", 4);
    pos_RIFFSize = _f.tellp();
    _f.write((const char *)&NULL32, 4);  // we'll come back to this later (pos_RIFFSize)
    _f.write("AVI ", 4);
    _f.write("LIST", 4);

    std::ofstream::streampos pos_hdrlSize = _f.tellp();
    _f.write((const char *)&NULL32, 4);
    _f.write("hdrl", 4);
    pos_aviMainHeader = _f.tellp();
    _f.write((const char *)&m_avimHeader, sizeof(AVIMAINHEADER));

    // streams begin
    int32_t i_streams = (int32_t)m_streamTypes.size();
    for (int32_t i_stream = 0; i_stream < i_streams; i_stream++)
    {
        _f.write("LIST", 4);
        std::ofstream::streampos pos_strlSize = _f.tellp();
        _f.write((const char *)&NULL32, 4);

        _f.write("strl", 4);
        pos_streamHeader[i_stream] = _f.tellp();
        _f.write((const char *)&m_avisHeaders[i_stream], sizeof(AVISTREAMHEADER));

        _f.write("strf", 4);
        if (m_streamTypes[i_stream] == avilib_Video)
        {
            i_tmp = sizeof(BITMAPINFO);
            _f.write((const char *)&i_tmp, 4);
            _f.write((const char *)&m_bitmapInfo, sizeof(BITMAPINFO));
        }
        else if (m_streamTypes[i_stream] == avilib_Audio)
        {
            m_waveFormat.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
            i_tmp                      = sizeof(WAVEFORMATEXTENSIBLE);
            _f.write((const char *)&i_tmp, 4);
            _f.write((const char *)&m_waveFormat, sizeof(WAVEFORMATEXTENSIBLE));
        }

        if (m_openDML)
        {
            uint32_t num_stdIndexes = 128;
            if (m_reservedFrames != 0)
            {
                num_stdIndexes = (m_reservedFrames + ((STDINDEXSIZE - sizeof(AVISTDINDEX)) / sizeof(AVISTDINDEX_ENTRY)) - 1) /
                                 ((STDINDEXSIZE - sizeof(AVISTDINDEX)) / sizeof(AVISTDINDEX_ENTRY));
                if (!num_stdIndexes)
                {
                    m_openDML = false;  // just guessing
                }
            }
            else
            {
                m_reservedFrames = num_stdIndexes * ((STDINDEXSIZE - sizeof(AVISTDINDEX)) / sizeof(AVISTDINDEX_ENTRY));
            }

            if (m_openDML)
            {
                size_t clearsize = sizeof(AVISUPERINDEX) + num_stdIndexes * sizeof(AVISUPERINDEX_ENTRY);

                // cf. http://msdn.microsoft.com/en-us/library/windows/desktop/ff625871(v=vs.85).aspx
                m_superIdxs[i_stream].fcc            = 'xdni';
                m_superIdxs[i_stream].nEntriesInUse  = 1;  // runtime adapted
                m_superIdxs[i_stream].cb             = (uint32_t)clearsize - 8;
                m_superIdxs[i_stream].wLongsPerEntry = 4;
                m_superIdxs[i_stream].bIndexSubType  = 0;  //[ AVI_INDEX_2FIELD | 0 ]
                m_superIdxs[i_stream].bIndexType     = AVI_INDEX_OF_INDEXES;
                m_superIdxs[i_stream].dwChunkId      = generate_fcc(m_streamTypes[i_stream] == avilib_Audio ? "wb" : "dc", i_stream);
                m_superIdxs[i_stream].dwReserved[0]  = 0;
                m_superIdxs[i_stream].dwReserved[1]  = 0;
                m_superIdxs[i_stream].dwReserved[2]  = 0;

                pos_odmlSuperIdx[i_stream] = _f.tellp();
                uint8_t *p                 = new uint8_t[clearsize];
                memset(p, 0x0, clearsize);
                _f.write((const char *)p, clearsize);
                delete[] p;
                // super index later -
            }
        }

        // write strl size
        off_tmp              = _f.tellp();
        uint32_t ui_strlSize = (uint32_t)(off_tmp - pos_strlSize) - 4;
        _f.seekp(pos_strlSize);
        _f.write((const char *)&ui_strlSize, 4);
        _f.seekp(off_tmp);

        // ---

        if (m_openDML)
        {
            AVISTDINDEX std_index;
            std_index.fcc            = i_stream == 1 ? '10xi' : '00xi';
            std_index.wLongsPerEntry = sizeof(AVISTDINDEX_ENTRY) / sizeof(DWORD);
            std_index.bIndexSubType  = 0;  // must be 0
            std_index.dwChunkId      = generate_fcc(m_streamTypes[i_stream] == avilib_Audio ? "wb" : "dc", i_stream);
            std_index.qwBaseOffset   = 0;  // all dwOffsets in aIndex array are relative to this
            std_index.dwReserved     = 0;  // must be 0
            m_stdIndexes[i_stream].push_back(std_index);
        }
    }  // streams

    if (m_openDML)
    {
        _f.write("LIST", 4);
        i_tmp = 248 + 12;
        _f.write((const char *)&i_tmp, 4);
        _f.write("odml", 4);
        _f.write("dmlh", 4);
        i_tmp = 248;
        _f.write((const char *)&i_tmp, 4);
        pos_odmlExt                    = _f.tellp();
        uint8_t *p                     = new uint8_t[i_tmp];
        ODMLExtendedAVIHeader odml_ext = {0};
        memset(p, 0x0, i_tmp);
        memcpy(p, &odml_ext, sizeof(ODMLExtendedAVIHeader));
        _f.write((const char *)p, i_tmp);
        delete[] p;
    }

    // write hdrl size
    off_tmp              = _f.tellp();
    uint32_t ui_hdrlSize = (uint32_t)(_f.tellp() - pos_hdrlSize) - 4;
    _f.seekp(pos_hdrlSize);
    _f.write((const char *)&ui_hdrlSize, 4);
    _f.seekp(off_tmp);

    // movi LIST
    _f.write("LIST", 4);
    pos_moviListSize = _f.tellp();
    _f.write((const char *)&NULL32, 4);  // later ...
    pos_1stMoviStart = _f.tellp();
    _f.write("movi", 4);

    m_RIFF_size   = (uint32_t)_f.tellp() + sizeof(AVIOLDINDEX);
    m_totalFrames = 0;
    m_currBaseOff = 0;

    return _f.good();
}

bool AviWriter::close()
{
    if (_f.bad())
        return false;

    _f.seekp(pos_aviMainHeader);
    _f.write((const char *)&m_avimHeader, sizeof(AVIMAINHEADER));

    int32_t i_streams = (int32_t)m_streamTypes.size();

    if (m_openDML)
    {
        // write odml total frame count
        _f.seekp(pos_odmlExt);
        ODMLExtendedAVIHeader odml_ext = {static_cast<DWORD>(m_totalFrames)};
        _f.write((const char *)&odml_ext, sizeof(ODMLExtendedAVIHeader));

        // write "00xi"s at the very end
        for (int32_t stream_idx = 0; stream_idx < i_streams; stream_idx++)
        {
            std::deque<AVISUPERINDEX_ENTRY> deq_supIdxEntries;
            for (auto &pair : m_stdIndexEntries[stream_idx])
            {
                AVISTDINDEX &std_index                           = m_stdIndexes[stream_idx][pair.first];
                const std::deque<AVISTDINDEX_ENTRY> &deq_entries = pair.second;

                std_index.bIndexType    = AVI_INDEX_OF_CHUNKS;
                std_index.nEntriesInUse = (uint32_t)deq_entries.size();
                std_index.cb            = (uint32_t)(sizeof(AVISTDINDEX) - 8 + sizeof(AVISTDINDEX_ENTRY) * deq_entries.size());

                _f.seekp(0, std::ofstream::end);
                uint64_t pos_stdindex = _f.filepos();
                assert(_f.good());

                _f.write((const char *)&std_index, sizeof(AVISTDINDEX));

                uint8_t *p = new uint8_t[sizeof(AVISTDINDEX_ENTRY) * deq_entries.size()], *p0 = p;
                for (const AVISTDINDEX_ENTRY &entry : deq_entries)
                {
                    *(AVISTDINDEX_ENTRY *)p = entry;
                    p += sizeof(AVISTDINDEX_ENTRY);
                };
                _f.write((const char *)p0, sizeof(AVISTDINDEX_ENTRY) * deq_entries.size());
                delete[] p0;

                AVISUPERINDEX_ENTRY supidx_entry;
                supidx_entry.qwOffset   = pos_stdindex;
                supidx_entry.dwSize     = std_index.cb + 8;
                supidx_entry.dwDuration = static_cast<DWORD>(deq_entries.size());  // frame count (video)
                deq_supIdxEntries.push_back(supidx_entry);
            };

            // write that list to superindex
            _f.seekp(pos_odmlSuperIdx[stream_idx], std::ofstream::beg);
            _f.write((const char *)&m_superIdxs[stream_idx], sizeof(AVISUPERINDEX));
            for (AVISUPERINDEX_ENTRY &supidx_entry : deq_supIdxEntries)
            {
                _f.write((const char *)&supidx_entry, sizeof(AVISUPERINDEX_ENTRY));
            };
        }
    }

    // write stream headers
    for (int32_t k = 0; k < i_streams; k++)
    {
        if (m_streamTypes[k] == avilib_Audio && m_videoStreamIdx != ~0u)
        {
            uint32_t vrate = m_avisHeaders[m_videoStreamIdx].dwRate;
            uint32_t vscal = m_avisHeaders[m_videoStreamIdx].dwScale;
            if (vrate && m_avisHeaders[k].dwScale && m_waveFormat.Format.nChannels)
            {
                uint32_t seconds          = m_avimHeader.dwTotalFrames * vscal / vrate;  // fixme: no video, bad timescale
                uint32_t len              = m_avisHeaders[k].dwRate * seconds / m_avisHeaders[k].dwScale / m_waveFormat.Format.nChannels;
                m_avisHeaders[k].dwLength = len;
            }
        }
        _f.seekp(pos_streamHeader[k], std::ofstream::beg);
        _f.write((const char *)&m_avisHeaders[k], sizeof(AVISTREAMHEADER));
    }

    if (m_RIFF_idx == 0)
    {
        finishOld();
    }
    else
    {
        // write movi size without final "ixNN"s
        _f.seekp(0, std::ofstream::end);
        uint32_t riffAvixMoviSize = (uint32_t)((uint64_t)_f.filepos() - pos_moviListSize - 4);
        _f.seekp(pos_moviListSize, std::ofstream::beg);
        _f.write((const char *)&riffAvixMoviSize, 4);

        _f.seekp(0, std::ofstream::end);
        uint32_t riffAvixSize = (uint32_t)((uint64_t)_f.filepos() - pos_RIFFSize - 4);

        _f.seekp(pos_RIFFSize, std::ofstream::beg);
        _f.write((const char *)&riffAvixSize, 4);
    }

    reset();
    _f.close();
    return true;
}

void AviWriter::finishAvix()
{
    uint32_t riffAvixSize, riffAvixMoviSize;
    _f.seekp(0, std::ofstream::end);
    riffAvixSize     = (uint32_t)((uint64_t)_f.tellp() - pos_RIFFSize - 4);
    riffAvixMoviSize = (uint32_t)((uint64_t)_f.tellp() - pos_moviListSize - 4);

    _f.seekp(pos_RIFFSize, std::ofstream::beg);
    _f.write((const char *)&riffAvixSize, 4);

    _f.seekp(pos_moviListSize, std::ofstream::beg);
    _f.write((const char *)&riffAvixMoviSize, 4);
}

void AviWriter::finishOld()
{
    for (int32_t stream_idx = 0; stream_idx < 1; stream_idx++)
    {
        _f.seekp(0, std::ofstream::end);
        uint64_t moviSize = (uint64_t)_f.filepos() - pos_moviListSize - 4;
        _f.seekp(pos_moviListSize, std::ofstream::beg);
        _f.write((const char *)&moviSize, 4);
    }

    _f.seekp(0, std::ofstream::end);
    _f.clear();
    uint32_t sz = (uint32_t)(m_oldIndexEntries.size() * sizeof(AVIOLDINDEX));
    _f.write("idx1", 4);
    _f.write((const char *)&sz, 4);

    for (AVIOLDINDEX &entry : m_oldIndexEntries)
        _f.write((const char *)&entry, sizeof(AVIOLDINDEX));

    // write file size right after "RIFF"
    _f.seekp(0, std::ofstream::end);
    uint32_t s_ = (uint32_t)_f.tellp() - 8;
    _f.seekp(pos_RIFFSize, std::ofstream::beg);
    _f.write((const char *)&s_, 4);
}

bool AviWriter::writeFrame(uint8_t stream_idx, void *data)
{
    return writeData(stream_idx, data, m_bitmapInfo.biSizeImage);
}

bool AviWriter::writeData(uint8_t stream_idx, void *data, uint32_t len)
{
    if (_f.bad())
        return false;
    char message[32];

    if (m_reservedFrames <= m_totalFrames)
        return false;

    FOURCC fourcc = m_streamTypes[stream_idx] == avilib_Video ? generate_fcc("dc", stream_idx) : generate_fcc("wb", stream_idx);

    uint32_t ui_next_RIFF_size = m_RIFF_size + len + 8;
    if (ui_next_RIFF_size >= m_currMaxRIFFSize)
    {
        if (m_RIFF_idx == 0)
        {
            // write old index
            finishOld();
            m_currMaxRIFFSize = 4ULL * 1000 * 1000 * 1000;
        }
        else
        {
            finishAvix();
        }

        _f.seekp(0, std::ofstream::end);

        sprintf(message, "#%u: RIFFSize = %u", m_totalFrames, m_RIFF_size);
        log(message);

        m_RIFF_idx++;
        m_RIFF_size = 24;
        _f.write("RIFF", 4);
        pos_RIFFSize = _f.filepos();
        _f.write((const char *)&NULL32, 4);  // we'll come back to this later (pos_RIFFSize)
        _f.write("AVIX", 4);

        _f.write("LIST", 4);
        pos_moviListSize = _f.filepos();
        _f.write((const char *)&NULL32, 4);  // later ...
        _f.write("movi", 4);
    }

    m_RIFF_size += len + 8;

    if (m_RIFF_idx == 0)
    {
        m_RIFF_size += sizeof(AVIOLDINDEX);
        std::ofstream::streamoff framePosforOldIndex = _f.tellp();
        framePosforOldIndex -= pos_1stMoviStart;  // relative to first "movi"

        AVIOLDINDEX entry;
        entry.dwChunkId = fourcc;
        entry.dwFlags   = AVIIF_KEYFRAME;  // for PCM Audio as well
        entry.dwSize    = len;
        entry.dwOffset  = (DWORD)framePosforOldIndex;
        m_oldIndexEntries.push_back(entry);
    }

    if ((m_stdIndexEntries[stream_idx][(int32_t)m_stdIndexes[stream_idx].size() - 1].size() + 1U) * sizeof(AVISTDINDEX_ENTRY) + sizeof(AVISTDINDEX) >=
        STDINDEXSIZE)
    {
        AVISTDINDEX std_index;
        std_index.fcc            = stream_idx ? '10xi' : '00xi';
        std_index.wLongsPerEntry = sizeof(AVISTDINDEX_ENTRY) / sizeof(DWORD);
        std_index.bIndexSubType  = 0;  // must be 0
        std_index.dwChunkId      = fourcc;
        std_index.qwBaseOffset   = (uint64_t)_f.filepos();  // all dwOffsets in aIndex array are relative to this
        std_index.dwReserved     = 0;                       // must be 0
        m_stdIndexes[stream_idx].push_back(std_index);

        m_currBaseOff = std_index.qwBaseOffset;

        m_superIdxs[stream_idx].nEntriesInUse++;
    }

    AVISTDINDEX_ENTRY std_entry = {static_cast<DWORD>((uint64_t)_f.tellp() + 8 - m_currBaseOff), len};
    m_stdIndexEntries[stream_idx][(int32_t)m_stdIndexes[stream_idx].size() - 1].push_back(std_entry);

    _f.write((const char *)&fourcc, 4);
    _f.write((const char *)&len, 4);
    _f.write((const char *)data, len);

    if (m_streamTypes[stream_idx] == avilib_Video)
    {
        m_avimHeader.dwTotalFrames++;
        m_avisHeaders[stream_idx].dwLength++;
    }
    m_totalFrames++;

    return true;
}

bool AviWriter::setVideoProperties(uint8_t stream_idx, int32_t width, int32_t height, uint32_t codec, uint32_t framesize, double rateHz)
{
    if (m_opened)
        return false;

    uint32_t Tmicr = int32_t(.5 + 1000000. / rateHz);

    m_avimHeader.dwMicroSecPerFrame = Tmicr;
    m_avimHeader.dwMaxBytesPerSec   = DWORD(.5 + framesize * rateHz);

    memset(&m_avisHeaders[stream_idx], 0x0, sizeof(AVISTREAMHEADER));
    AVISTREAMHEADER &aviVideoStreamHeader = m_avisHeaders[stream_idx];

    aviVideoStreamHeader.fcc        = 'hrts';
    aviVideoStreamHeader.cb         = sizeof(AVISTREAMHEADER) - 8;
    aviVideoStreamHeader.fccType    = 'sdiv';
    aviVideoStreamHeader.fccHandler = BI_RGB;
    aviVideoStreamHeader.dwScale    = 1;
    aviVideoStreamHeader.dwQuality  = -1;

    int32_t rate, scale;
    if (cancel(int32_t(.5 + 1000. * rateHz), 1000, rate, scale))
    {
        aviVideoStreamHeader.dwScale = scale;
        aviVideoStreamHeader.dwRate  = rate;
    }
    else
    {
        aviVideoStreamHeader.dwScale = 1;
        aviVideoStreamHeader.dwRate  = uint32_t(.5 + rateHz);
    }

    m_avimHeader.dwMaxBytesPerSec      = framesize * uint32_t(.5 + rateHz);
    m_avimHeader.dwSuggestedBufferSize = framesize;
    m_avimHeader.dwWidth               = width;
    m_avimHeader.dwHeight              = height;

    aviVideoStreamHeader.fccType               = 'sdiv';
    aviVideoStreamHeader.fccHandler._          = codec;
    aviVideoStreamHeader.dwSuggestedBufferSize = framesize;
    aviVideoStreamHeader.rcFrame.top = aviVideoStreamHeader.rcFrame.left = 0;
    aviVideoStreamHeader.rcFrame.bottom                                  = height;
    aviVideoStreamHeader.rcFrame.right                                   = width;

    m_bitmapInfo.biWidth       = width;
    m_bitmapInfo.biHeight      = height;
    m_bitmapInfo.biCompression = codec;
    m_bitmapInfo.biSizeImage   = framesize;

    m_avimHeader.dwStreams++;

    m_videoStreamIdx          = stream_idx;
    m_streamTypes[stream_idx] = avilib_Video;
    return true;
}

bool AviWriter::setAudioProperties(uint8_t stream_idx,
                                   int16_t format,
                                   int8_t channels,
                                   uint32_t samplesPerSecond,
                                   uint32_t avgBytesPerSecond,
                                   uint16_t bitsPerSample,
                                   uint16_t blockAlign)
{
    if (m_opened)
        return false;

    m_waveFormat.Format.wFormatTag      = format;
    m_waveFormat.Format.nChannels       = channels;
    m_waveFormat.Format.nSamplesPerSec  = samplesPerSecond;
    m_waveFormat.Format.nAvgBytesPerSec = avgBytesPerSecond;
    m_waveFormat.Format.nBlockAlign     = blockAlign;
    m_waveFormat.Format.wBitsPerSample  = bitsPerSample;
    m_waveFormat.Format.cbSize          = 0;

    memset(&m_avisHeaders[stream_idx], 0x0, sizeof(AVISTREAMHEADER));
    AVISTREAMHEADER &aviAudioStreamHeader = m_avisHeaders[stream_idx];
    aviAudioStreamHeader.fcc              = 'hrts';
    aviAudioStreamHeader.cb               = sizeof(AVISTREAMHEADER) - 8;
    aviAudioStreamHeader.fccType          = 'sdua';

    aviAudioStreamHeader.fccType               = 'sdua';
    aviAudioStreamHeader.fccHandler._          = 0x0000;
    aviAudioStreamHeader.dwSuggestedBufferSize = avgBytesPerSecond * channels;
    aviAudioStreamHeader.dwRate                = samplesPerSecond * bitsPerSample;
    aviAudioStreamHeader.dwSampleSize          = bitsPerSample;
    aviAudioStreamHeader.dwScale               = bitsPerSample;

    m_waveFormat.Samples.wValidBitsPerSample = bitsPerSample;
    m_waveFormat.Samples.wReserved           = 0;
    m_waveFormat.Samples.wSamplesPerBlock    = blockAlign;
    m_waveFormat.dwChannelMask               = 0x0;
    for (int32_t i = 0; i < channels; ++i)
        m_waveFormat.dwChannelMask |= 1 << i;

    m_avimHeader.dwStreams++;

    m_streamTypes[stream_idx] = avilib_Audio;
    return true;
}

bool AviWriter::setFrameCount(uint8_t, uint32_t count)
{
    if (m_opened)
        return false;

    m_reservedFrames = count;

    return true;
}

#if defined(_WIN32) || defined(__clang__)
#    define LLU "%llu"
#elif defined(__gnug__)
#    define LLU "%lu"
#else
#    define LLU "%ull"
#endif

AviReader::AviReader() {}

AviReader::~AviReader()
{
    _f.close();
}

// https://msdn.microsoft.com/de-de/library/ms779636.aspx
// http://www.onicos.com/staff/iz/formats/wav.html

bool AviReader::open(const char *filename)
{
    char tag[5]            = {0};
    uint32_t size          = 0;
    bool b_audio           = false;
    int32_t i_streamHeader = 0;
    m_useMovieOffset       = false;
    char message[512];
    clear_log();

    m_moviOffs.clear();
    _f.open(filename, std::ifstream::binary);
    if (!_f.good())
    {
        log("Cannot parse, file is bad.");
        return false;
    }

    _f.seekg(0, std::ifstream::beg);
    for (; _f.good();)
    {
        int64_t pos = _f.tellg();
        if (!_f.read(tag, 4))
            break;

        sprintf(message, "%s @ " LLU, tag, (uint64_t)pos);
        log(message);

        if (*(uint32_t *)tag == (uint32_t)'KNUJ')
        {
            _f.read((char *)&size, 4);
            _f.seekg(size, std::ifstream::cur);
        }
        else if (*(uint32_t *)tag == (uint32_t)'FFIR')
        {
            _f.read((char *)&size, 4);  // file size
        }
        else if (*(uint32_t *)tag == (uint32_t)' IVA')
        {
            continue;
        }
        else if (*(uint32_t *)tag == (uint32_t)'XIVA')
        {
            continue;  // OPENDML: AVIX, further RIFF chunk(s)
        }
        else if (*(uint32_t *)tag == (uint32_t)'TSIL')
        {
            _f.read((char *)&size, 4);  // 1st: file size
        }
        else if (*(uint32_t *)tag == (uint32_t)'lrdh')
        {
            continue;
        }
        else if (*(uint32_t *)tag == (uint32_t)'hiva')
        {
            _f.read((char *)&size, 4);  // AVIMAINHEADER
            if (size != sizeof(AVIMAINHEADER) - 8)
                return false;
            m_avimHeader.fcc = *(FOURCC *)tag;
            m_avimHeader.cb  = size;
            _f.read((char *)&m_avimHeader + 8, m_avimHeader.cb);
            sprintf(message,
                    "AVIMAINHEADER (\n  dwFlags=0x%x,\n  dwWidth=%i,\n  dwHeight=%i,\n  dwStreams=%i,\n  dwInitialFrames=%i,\n  "
                    "dwMaxBytesPerSec=%i\n  dwMicroSecPerFrame=%i\n  dwPaddingGranularity=%i\n  dwSuggestedBufferSize=%i\n  dwTotalFrames=%i\n)",
                    m_avimHeader.dwFlags, m_avimHeader.dwWidth, m_avimHeader.dwHeight, m_avimHeader.dwStreams, m_avimHeader.dwInitialFrames,
                    m_avimHeader.dwMaxBytesPerSec, m_avimHeader.dwMicroSecPerFrame, m_avimHeader.dwPaddingGranularity,
                    m_avimHeader.dwSuggestedBufferSize, m_avimHeader.dwTotalFrames);
            log(message);
        }
        else if (*(uint32_t *)tag == (uint32_t)'lrts')
        {
            if (i_streamHeader >= 2)
                return false;
            _f.read((char *)&m_avisHeader[i_streamHeader], sizeof(AVISTREAMHEADER));
            b_audio = m_avisHeader[i_streamHeader].fccType._ == (uint32_t)'sdua';
            if (!b_audio)
                m_videoStreamIdx = i_streamHeader;

            sprintf(message,
                    "AVISTREAMHEADER (\n  fccType=%c%c%c%c,\n  fccHandler=%c%c%c%c,\n  dwFlags=%i,\n  wPriority=%i,\n  wLanguage=%i,\n  "
                    "dwInitialFrames=%i\n  dwScale=%i\n  dwRate=%i\n  dwStart=%i\n  dwLength=%i\n  dwSuggestedBufferSize=%i\n  dwQuality=%i\n  "
                    "dwSampleSize=%i\n  rcFrame.left=%i\n  rcFrame.top=%i\n  rcFrame.right=%i\n  rcFrame.bottom=%i\n)",
                    m_avisHeader[i_streamHeader].fccType.fcc[0] ? m_avisHeader[i_streamHeader].fccType.fcc[0] : '?',
                    m_avisHeader[i_streamHeader].fccType.fcc[1] ? m_avisHeader[i_streamHeader].fccType.fcc[1] : '?',
                    m_avisHeader[i_streamHeader].fccType.fcc[2] ? m_avisHeader[i_streamHeader].fccType.fcc[2] : '?',
                    m_avisHeader[i_streamHeader].fccType.fcc[3] ? m_avisHeader[i_streamHeader].fccType.fcc[3] : '?',
                    m_avisHeader[i_streamHeader].fccHandler.fcc[0] ? m_avisHeader[i_streamHeader].fccHandler.fcc[0] : '?',
                    m_avisHeader[i_streamHeader].fccHandler.fcc[1] ? m_avisHeader[i_streamHeader].fccHandler.fcc[1] : '?',
                    m_avisHeader[i_streamHeader].fccHandler.fcc[2] ? m_avisHeader[i_streamHeader].fccHandler.fcc[2] : '?',
                    m_avisHeader[i_streamHeader].fccHandler.fcc[3] ? m_avisHeader[i_streamHeader].fccHandler.fcc[3] : '?',
                    m_avisHeader[i_streamHeader].dwFlags, m_avisHeader[i_streamHeader].wPriority, m_avisHeader[i_streamHeader].wLanguage,
                    m_avisHeader[i_streamHeader].dwInitialFrames, m_avisHeader[i_streamHeader].dwScale, m_avisHeader[i_streamHeader].dwRate,
                    m_avisHeader[i_streamHeader].dwStart, m_avisHeader[i_streamHeader].dwLength, m_avisHeader[i_streamHeader].dwSuggestedBufferSize,
                    m_avisHeader[i_streamHeader].dwQuality, m_avisHeader[i_streamHeader].dwSampleSize, m_avisHeader[i_streamHeader].rcFrame.left,
                    m_avisHeader[i_streamHeader].rcFrame.top, m_avisHeader[i_streamHeader].rcFrame.right,
                    m_avisHeader[i_streamHeader].rcFrame.bottom);
            log(message);
            i_streamHeader++;
        }
        else if (*(uint32_t *)tag == (uint32_t)'hrts')
        {
            // int z=0;
        }
        else if (*(uint32_t *)tag == (uint32_t)'frts')
        {
            _f.read((char *)&size, 4);
            if (b_audio)
            {
                _f.read((char *)&m_waveformat, std::min(size, (uint32_t)sizeof(WAVEFORMATEX)));
                if (m_waveformat.wFormatTag == WAVE_FORMAT_EXTENSIBLE)
                {
                    _f.seekg(-(std::streamoff)sizeof(WAVEFORMATEX), std::ifstream::cur);
                    _f.read((char *)&m_waveformatExt, (uint32_t)sizeof(WAVEFORMATEXTENSIBLE));
                    _f.seekg(size - sizeof(WAVEFORMATEXTENSIBLE), std::ifstream::cur);
                    sprintf(message,
                            "WAVEFORMATEXTENSIBLE (\n  wFormatTag=0x%04x,\n  nChannels=%i,\n  nAvgBytesPerSec=%i,\n  nSamplesPerSec=%i,\n  "
                            "wBitsPerSample=%i,\n  nBlockAlign=%i,\n  SubFormat=...,\n  wSamplesPerBlock=%i,\n  dwChannelMask=%i\n)",
                            m_waveformat.wFormatTag, m_waveformat.nChannels, m_waveformat.nAvgBytesPerSec, m_waveformat.nSamplesPerSec,
                            m_waveformat.wBitsPerSample, m_waveformat.nBlockAlign, m_waveformatExt.Samples.wSamplesPerBlock,
                            m_waveformatExt.dwChannelMask);
                }
                else if (m_waveformat.wFormatTag == WAVE_FORMAT_MPEG)
                {
                    _f.seekg(-(std::streamoff)sizeof(WAVEFORMATEX), std::ifstream::cur);
                    _f.read((char *)&m_waveformatMpeg12, (uint32_t)sizeof(MPEG1WAVEFORMAT));
                    _f.seekg((std::streamoff)size - sizeof(MPEG1WAVEFORMAT), std::ifstream::cur);
                    sprintf(message,
                            "MPEG1WAVEFORMAT (\n  wFormatTag=0x%04x,\n  nChannels=%i,\n  nAvgBytesPerSec=%i,\n  nSamplesPerSec=%i,\n  "
                            "wBitsPerSample=%i,\n  nBlockAlign=%i,\n  dwHeadBitrate=%i,\n  dwPTSHigh=%i,\n  dwPTSLow=%i,\n  fwHeadFlags=%i,\n  "
                            "fwHeadLayer=%i,\n  fwHeadMode=%i,\n  fwHeadModeExt=%i,\n  wHeadEmphasis%i\n)",
                            m_waveformat.wFormatTag, m_waveformat.nChannels, m_waveformat.nAvgBytesPerSec, m_waveformat.nSamplesPerSec,
                            m_waveformat.wBitsPerSample, m_waveformat.nBlockAlign, m_waveformatMpeg12.dwHeadBitrate, m_waveformatMpeg12.dwPTSHigh,
                            m_waveformatMpeg12.dwPTSLow, m_waveformatMpeg12.fwHeadFlags, m_waveformatMpeg12.fwHeadLayer,
                            m_waveformatMpeg12.fwHeadMode, m_waveformatMpeg12.fwHeadModeExt, m_waveformatMpeg12.wHeadEmphasis);
                }
                else if (m_waveformat.wFormatTag == WAVE_FORMAT_MPEGLAYER3)
                {
                    _f.seekg(-(std::streamoff)sizeof(WAVEFORMATEX), std::ifstream::cur);
                    _f.read((char *)&m_waveformatMP3, (uint32_t)sizeof(MPEGLAYER3WAVEFORMAT));
                    _f.seekg(size - sizeof(MPEGLAYER3WAVEFORMAT), std::ifstream::cur);
                    sprintf(
                        message,
                        "MPEGLAYER3WAVEFORMAT (\n  wFormatTag=0x%04x,\n  nChannels=%i,\n  nAvgBytesPerSec=%i,\n  nSamplesPerSec=%i,\n  "
                        "wBitsPerSample=%i,\n  nBlockAlign=%i\n  fdwFlags%i,\n  nBlockSize%i,\n  nCodecDelay%i,\n  nFramesPerBlock%i,\n  wID=%i\n)",
                        m_waveformat.wFormatTag, m_waveformat.nChannels, m_waveformat.nAvgBytesPerSec, m_waveformat.nSamplesPerSec,
                        m_waveformat.wBitsPerSample, m_waveformat.nBlockAlign, m_waveformatMP3.fdwFlags, m_waveformatMP3.nBlockSize,
                        m_waveformatMP3.nCodecDelay, m_waveformatMP3.nFramesPerBlock, m_waveformatMP3.wID);
                }
                else
                {
                    _f.seekg(size - sizeof(WAVEFORMATEX), std::ifstream::cur);
                    sprintf(message,
                            "WAVEFORMATEX (\n  wFormatTag=0x%04x,\n  nChannels=%i,\n  nAvgBytesPerSec=%i,\n  nSamplesPerSec=%i,\n  "
                            "wBitsPerSample=%i,\n  nBlockAlign=%i\n)",
                            m_waveformat.wFormatTag, m_waveformat.nChannels, m_waveformat.nAvgBytesPerSec, m_waveformat.nSamplesPerSec,
                            m_waveformat.wBitsPerSample, m_waveformat.nBlockAlign);
                }
                log(message);
            }
            else
            {
                _f.read((char *)&m_bitmapInfo, std::min(size, (uint32_t)sizeof(BITMAPINFO)));
                if (size > sizeof(BITMAPINFO))
                    _f.seekg(size - sizeof(BITMAPINFO), std::ifstream::cur);
                sprintf(message,
                        "BITMAPINFO (\n  biWidth=%i,\n  biHeight=%i,\n  biPlanes=%i,\n  biBitCount=%i,\n  biCompression=%s,\n  biSizeImage=%i,\n  "
                        "biXPelsPerMeter=%i,\n  biYPelsPerMeter=%i,\n  biClrUsed=%i,\n  biClrImportant=%i,\n)",
                        m_bitmapInfo.biWidth, m_bitmapInfo.biHeight, m_bitmapInfo.biPlanes, m_bitmapInfo.biBitCount, m_bitmapInfo.biCompression.fcc,
                        m_bitmapInfo.biSizeImage, m_bitmapInfo.biXPelsPerMeter, m_bitmapInfo.biYPelsPerMeter, m_bitmapInfo.biClrUsed,
                        m_bitmapInfo.biClrImportant);
                log(message);
            }
        }
        else if (*(uint32_t *)tag == (uint32_t)'OFNI')
        {
            continue;
        }
        else if (*(uint32_t *)tag == (uint32_t)'TFSI')
        {
            _f.read((char *)&size, 4);
            _f.seekg(size, std::ifstream::cur);
        }
        else if (*(uint32_t *)tag == (uint32_t)'nrts')
        {
            _f.read((char *)&size, 4);
            _f.seekg(size - 4, std::ifstream::cur);
        }
        else if (*(uint32_t *)tag == (uint32_t)'ivom')
        {
            // the movie list, here is the data.
            m_moviOffs.push_back((size_t)_f.tellg() - 4);
            _f.seekg(size - 4, std::ifstream::cur);
        }
        else if (*(uint32_t *)tag == (uint32_t)'1xdi')
        {
            _f.read((char *)&size, 4);
            uint8_t *p = new uint8_t[size], *p0 = p;
            size = (uint32_t)_f.read((char *)p, size).gcount();
            for (size_t i = 0; i < size / sizeof(AVIOLDINDEX); i++)
            {
                AVIOLDINDEX *idx = (AVIOLDINDEX *)p;
                p += sizeof(AVIOLDINDEX);

                if (idx->dwChunkId._ == ' cer')
                    continue;

                DMLINDEX dml_index;
                dml_index.dwChunkId  = idx->dwChunkId;
                dml_index.u32_flags  = idx->dwFlags;
                dml_index.i64_offset = idx->dwOffset;
                dml_index.u32_size   = idx->dwSize;
                std::string fcc      = idx->dwChunkId.fcc;
                int32_t stream_idx   = atoi(fcc.substr(0, 2).c_str());
                m_frameIdxs[stream_idx].push_back(dml_index);
                /*
                                                switch(m_videoStreamIdx){
                                                case 0:
                                                        if( idx->dwChunkId._ == (uint32_t)'cd00' || idx->dwChunkId._ == (uint32_t)'bd00' )//'bw10' )
                                                                m_frameIdxs[stream_idx].push_back(dml_index);
                                                        break;
                                                case 1:
                                                        if( idx->dwChunkId._ == (uint32_t)'cd10' || idx->dwChunkId._ == (uint32_t)'bd10' )//'bw10' )
                                                                m_frameIdxs[stream_idx].push_back(dml_index);
                                                        break;
                                                }
                 */
            }
            sprintf(message, "%x AVIOLDINDEX", size / (uint32_t)sizeof(AVIOLDINDEX));
            log(message);
            delete[] p0;
        }
        else if (*(uint32_t *)tag == (uint32_t)'lmdo')
        {
            continue;
        }
        else if (*(uint32_t *)tag == (uint32_t)'hlmd')
        {
            _f.read((char *)&size, 4);
            _f.read((char *)&m_odmlExt, sizeof(ODMLExtendedAVIHeader));
            _f.seekg(size - sizeof(ODMLExtendedAVIHeader), std::ifstream::cur);
            sprintf(message, "ODMLExtendedAVIHeader ( dwTotalFrames=%i )", m_odmlExt.dwTotalFrames);
            log(message);
        }
        else if (*(uint32_t *)tag == (uint32_t)'lhmd')
        {
            _f.read((char *)&size, 4);
            _f.seekg(size, std::ifstream::cur);
        }
        else if (*(uint32_t *)tag == (uint32_t)'xdni')
        {
            _f.read((char *)&size, 4);  // AVISUPERINDEX
            std::streampos superindex_end = _f.tellg() + (std::streampos)size;

            AVISUPERINDEX super_index;
            super_index.fcc._ = *((uint32_t *)&tag);
            super_index.cb    = size;
            _f.read((char *)&super_index + 8, sizeof(AVISUPERINDEX) - 8);

            std::string fcc    = super_index.dwChunkId.fcc;
            int32_t stream_idx = atoi(fcc.substr(0, 2).c_str());

            assert(0 == super_index.bIndexSubType);
            if (0 != super_index.bIndexSubType)
                log("Warning: 0 != super_index.bIndexSubType");
            if (AVI_INDEX_OF_INDEXES != super_index.bIndexType)
                log("Warning: AVI_INDEX_OF_INDEXES != super_index.bIndexType");
            std::deque<AVISUPERINDEX_ENTRY> entries;

            for (uint32_t i = 0; i < super_index.nEntriesInUse; i++)
            {
                AVISUPERINDEX_ENTRY entry;
                _f.read((char *)&entry, sizeof(AVISUPERINDEX_ENTRY));
                entries.push_back(entry);
            }

            std::sprintf(message, "%ux AVISUPERINDEX_ENTRY (%s)", super_index.nEntriesInUse, fcc.c_str());
            log(message);

            for (AVISUPERINDEX_ENTRY &entry : entries)
            {
                _f.seekg(entry.qwOffset, std::ifstream::beg);
                AVISTDINDEX std_index;
                memset(&std_index, 0x0, sizeof(AVISTDINDEX));
                _f.read((char *)&std_index, sizeof(AVISTDINDEX));

                if (std_index.dwChunkId._ == ' cer')
                    continue;

                std::array<uint32_t, 6> allowed = {(uint32_t)'cd00', (uint32_t)'bd00', (uint32_t)'cd10',
                                                   (uint32_t)'bd10', (uint32_t)'bw00', (uint32_t)'bw10'};

                if (std::find(allowed.begin(), allowed.end(), std_index.dwChunkId._) != allowed.end())
                {

                    // not for audio:
                    if (AVI_INDEX_OF_CHUNKS != std_index.bIndexType)
                        log("Warning: AVI_INDEX_OF_CHUNKS != std_index.bIndexType");
                    if (0 != std_index.bIndexSubType)
                        log("Warning: 0 != std_index.bIndexSubType");

                    // if( std_index.fcc._ == (uint32_t)'00xi' )
                    {
                        uint8_t *p = new uint8_t[std_index.cb], *p0 = p;
                        _f.read((char *)p, std_index.cb);
                        for (uint32_t j = 0; j < std_index.nEntriesInUse; j++)
                        {
                            AVISTDINDEX_ENTRY *idx = (AVISTDINDEX_ENTRY *)p;
                            p += sizeof(AVISTDINDEX_ENTRY);

                            DMLINDEX dml_index;
                            dml_index.dwChunkId  = std_index.dwChunkId;
                            dml_index.u32_flags  = 0;
                            dml_index.i64_offset = std_index.qwBaseOffset + idx->dwOffset;  // + 4;
                            dml_index.u32_size   = idx->dwSize & 0x7FFFFFFF;
                            if (dml_index.u32_size > m_bitmapInfo.biSizeImage)
                            {
                                // int z=0;
                            }

                            odml_frameIdxs[stream_idx].push_back(dml_index);
                        }
                        delete[] p0;
                    }
                }
                _f.clear();
            }  // each superindex entry
            m_superIndex[stream_idx] = super_index;
            _f.clear();
            _f.seekg(superindex_end, std::ifstream::beg);
        }
        else if (*(uint32_t *)tag == (uint32_t)'prpv')
        {
            // this does nothing and has never been tested.
            _f.read((char *)&size, 4);
            VIDEO_PROP_HEADER vprop_header;
            _f.read((char *)&vprop_header, std::min((size_t)size, sizeof(VIDEO_PROP_HEADER)));
            VIDEO_PROP_HEADER::VIDEO_FIELD_DESC *p_field_desc =
                (VIDEO_PROP_HEADER::VIDEO_FIELD_DESC *)malloc(sizeof(VIDEO_PROP_HEADER::VIDEO_FIELD_DESC) * vprop_header.nbFieldPerFrame);
            _f.read((char *)p_field_desc, vprop_header.nbFieldPerFrame * sizeof(VIDEO_PROP_HEADER::VIDEO_FIELD_DESC));
            free(p_field_desc);
        }
        else if (*(uint32_t *)tag == (uint32_t)'tadT' /* Adobe Premiere Timecode */)
        {
            continue;
        }
        else
        {
            _f.read((char *)&size, 4);
            _f.seekg(size, std::ifstream::cur);
            // FIXME: check size is smaller than current parent element's size
        }
    }

    if (!odml_frameIdxs.empty())
    {
        m_frameIdxs.swap(odml_frameIdxs);
        log("using OpenDML index");
    }

    _f.clear();
    _f.seekg(0, std::ifstream::beg);

    if (m_moviOffs.empty())
        return false;

    return true;
}

int32_t AviReader::readFrame(uint32_t idx, uint32_t stream, void *p_data)
{
    // if( _f.bad() ) return false;
    // if( _f.eof() ) return false;
    char tag_size[8];
    int32_t i_read = 0;
    uint64_t  // ui64_tmpMoviOff,
        ui64_moviOffset = 0;
    DMLINDEX *p_aviIndex;

    if (idx >= m_frameIdxs[stream].size())
        return false;

    // if( idx >= m_frameIdxs[m_videoStreamIdx].size() ){
    // 	idx -= m_frameIdxs[m_videoStreamIdx].size();
    // 	if( idx >= odml_frameIdxs[m_videoStreamIdx].size() )
    // 		return false;
    // 	aviIndex = &odml_frameIdxs[m_videoStreamIdx][idx];
    // }
    // else
    p_aviIndex = &m_frameIdxs[stream][idx];

    if (!p_aviIndex->u32_size)
        return true;

    if (m_useMovieOffset)
        goto skip_search;

    _f.clear();

    // try without offset
    _f.seekg(p_aviIndex->i64_offset, std::ifstream::beg);
    _f.read(tag_size, 8);
    i_read += 8;
    if (p_aviIndex->dwChunkId._ != *((uint32_t *)tag_size))
    {
        // try with offset
    skip_search:
        // ui64_tmpMoviOff = _f.tellg();
        // for_each(m_moviOffs.begin(),m_moviOffs.end(),[&](uint64_t &off){
        //	if( ui64_tmpMoviOff >= off )
        //		ui64_moviOffset = off;
        //});

        ui64_moviOffset = m_moviOffs[0];
        _f.seekg(p_aviIndex->i64_offset + ui64_moviOffset, std::ifstream::beg);
        _f.read((char *)tag_size, 8);
        i_read += 8;
        m_useMovieOffset = p_aviIndex->dwChunkId._ == *((uint32_t *)tag_size);
        if (!m_useMovieOffset)
            _f.seekg(p_aviIndex->i64_offset, std::ifstream::beg);  // probably no chunk header.
    }

    // uint64_t tell = _f.tellg().seekpos();
    //_f.read( (char *)p_data, p_aviIndex->u32_size );
    // return true;

    return (int32_t)_f.read((char *)p_data, p_aviIndex->u32_size).gcount();
}

bool AviReader::close()
{
    if (_f.bad())
        return false;

    _f.close();

    return true;
}

bool AviReader::getSize(int32_t &width, int32_t &height)
{
    if (_f.bad())
        return false;

    width  = m_avimHeader.dwWidth;
    height = m_avimHeader.dwHeight;
    return true;
}

bool AviReader::getFrameRate(double &rateHz)
{
    if (_f.bad())
        return false;

    rateHz = 1000000. / m_avimHeader.dwMicroSecPerFrame;
    return true;
}

bool AviReader::getFormat(uint32_t, avilib_BITMAPINFO &bm_info)
{
    if (_f.bad())
        return false;

    memcpy(&bm_info, &m_bitmapInfo, sizeof(avilib_BITMAPINFO));
    return true;
}

bool AviReader::getFormat(uint32_t, avilib_WAVEFORMATEX &wav_info)
{
    if (_f.bad())
        return false;

    memcpy(&wav_info, &m_waveformat, sizeof(avilib_WAVEFORMATEX));
    return true;
}

bool AviReader::getFrameCount(uint32_t stream, uint32_t &count)
{
    if (_f.bad())
        return false;
    count = (uint32_t)m_frameIdxs[stream].size();

    return true;
}
bool AviReader::getCodec(uint32_t stream, uint32_t &codec)
{
    if (_f.bad())
        return false;

    if (stream == (uint32_t)m_videoStreamIdx)
        codec = (uint32_t)m_bitmapInfo.biCompression._;
    else
        codec = (uint32_t)m_waveformat.wFormatTag;
    return true;
}
bool AviReader::getStreamCount(uint32_t &count)
{
    if (_f.bad())
        return false;

    count = (uint32_t)m_frameIdxs.size();
    return true;
}
bool AviReader::getAllocSize(uint32_t stream, uint32_t &size)
{
    if (_f.bad())
        return false;

    size = 0;
    for (const DMLINDEX &dmlIdx : m_frameIdxs[stream])
        size = std::max(size, dmlIdx.u32_size);
    size = std::max(size, stream == (uint32_t)m_videoStreamIdx ? m_bitmapInfo.biSizeImage : m_avisHeader[stream].dwSuggestedBufferSize);
    return true;
}
bool AviReader::getStreamType(uint32_t stream, avilib_streamtype_t &type)
{
    if (_f.bad())
        return false;

    type = (uint32_t)m_videoStreamIdx == stream ? avilib_Video : avilib_Audio;
    return true;
}

int32_t avilib::gcd(int32_t nom, int32_t denom)
{
    if (nom < denom)
    {
        int32_t tmp = nom;
        nom         = denom;
        denom       = tmp;
    }
    if (!(nom % denom))
        return denom;
    return gcd(denom, nom % denom);
}

bool avilib::cancel(int32_t nom, int32_t denom, int32_t &out_nom, int32_t &out_denom)
{
    if (!denom || !nom)
        return false;
    int32_t _gcd = gcd(nom, denom);

    if (_gcd)
    {
        out_nom   = nom / _gcd;
        out_denom = denom / _gcd;
        return true;
    }

    return false;
}

void LoggingObject::log(const std::string &message)
{
#ifdef DEBUG
    std::cout << message << endl;
#endif
    m_messages.push_back(message);
}
bool LoggingObject::getNextMessage(std::string &message)
{
    if (m_messages.empty())
        return false;
    message = m_messages.front();
    m_messages.pop_front();
    return true;
}
void LoggingObject::clear_log()
{
    m_messages.clear();
}

FOURCC avilib::generate_fcc(std::string id, uint8_t idx)
{
    char buff[3] = {0};
    FOURCC ret   = {0};
    sprintf(buff, "%02i", idx > 99 ? 99 : idx < 0 ? 0 : idx);
    ret.fcc[0] = buff[0];
    ret.fcc[1] = buff[1];
    ret.fcc[2] = id.c_str()[0];
    ret.fcc[3] = id.c_str()[1];
    return ret;
}
